package com.db4o.internal.odbgen;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;



import com.db4o.odbgen.OdbgenException;
import com.db4o.odbgen.plugins.Log;
import com.db4o.odbgen.plugins.xmlschema.Element;
import com.db4o.odbgen.plugins.xmlschema.XmlSchema;


/**
 * 
 * Creates an {@link XmlSchema} given a set of XmlSchema files.
 * @author liviug
 * 
 */
public class XmlSchemaParser {
    private List<File> _xmlSchemaFiles;
    private List<Element> _xmlSchemaElements;
    
    /**
     * Constructs the parser.
     * @param xmlSchemaFiles The files represenging XmlSchema. These files are
     * found inside input directory.
     */
    public XmlSchemaParser(List<File> xmlSchemaFiles){
        this._xmlSchemaFiles = xmlSchemaFiles;
    }

    /**
     * Parses the files and returns the corresponding XmlSchema.
     * @throws IOException 
     * @throws SAXException 
     * @throws OdbgenException 
     */
    public XmlSchema parseXmlSchema() throws ParserConfigurationException, SAXException, IOException, OdbgenException{
        this._xmlSchemaElements = new ArrayList<Element>();
        DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        for(File f : this._xmlSchemaFiles){
            try{
                Document doc = builder.parse(f);
                this.buildXmlSchemaElements(doc, this._xmlSchemaElements, f);
            }
            catch(SAXException e){
                OdbgenException oe = new OdbgenException(
                        e,
                        "The file '%s' is not an XmlSchema file. It will be ignored.",
                        f.getAbsoluteFile()
                );
                Log.write(oe);
            }
        }

        return new XmlSchema(this._xmlSchemaElements);
    }

    /**
     * Creates the XmlSchema elements given an xml document.
     * Adds created elements to the provided collection.
     * @param doc The xml document to be parsed.
     * @param schemaElements The collection used to add XmlSchema elements.
     * @param parentFile The file that contains the XmlSchema.
     * @throws OdbgenException 
     */
    private void buildXmlSchemaElements(
            Document doc, 
            List<Element> schemaElements,
            File parentFile
            ) throws OdbgenException {
        org.w3c.dom.Element xroot = doc.getDocumentElement(); 
        NodeList xrootchildren = xroot.getChildNodes();
        for(int i=0; i<xrootchildren.getLength(); i++){
            if(xrootchildren.item(i).getNodeType()==Node.ELEMENT_NODE){
                org.w3c.dom.Element xchild = (org.w3c.dom.Element)xrootchildren.item(i);
                schemaElements.add(this.buildXmlSchemaElementFromElement(xchild, null, parentFile));
            }
        }
    }

    /**
     * Builds a XmlSchema element given an xml node.
     * This recursively adds any child elements.
     * @param xmlElement The node used to create the XmlSchema element.
     * @param parent The parent XmlSchema element. This is null if there is no parent.
     * @param parentFile The file that contains the element.
     * @return The created XmlSchema element.
     * @throws OdbgenException 
     */
    private Element buildXmlSchemaElementFromElement(
            org.w3c.dom.Element xmlElement, 
            Element parent,
            File parentFile
            ) throws OdbgenException {
        
        String name = xmlElement.getTagName();
        List<Element> children = new ArrayList<Element>();
        String content = ""; //This is because xmlElement.getTextContent() returns the concatenated conent of xmlElement and all it's children. 

        Element element = new Element(name, parent, children, content);
        element.setFilePath(parentFile.getAbsolutePath());

        //Get child attributes
        NamedNodeMap xattributes = xmlElement.getAttributes();
        for(int i=0; i<xattributes.getLength(); i++){
            Attr xattribute = (Attr)xattributes.item(i);
            Element child = this.buildXmlSchemaElementFromAttribute(xattribute, element, parentFile);
            children.add(child);
        }

        //Get child nodes.
        NodeList childNodes = xmlElement.getChildNodes();
        for(int i=0; i<childNodes.getLength(); i++){
            if(childNodes.item(i).getNodeType()==Node.ELEMENT_NODE){
                org.w3c.dom.Element xchildElement = (org.w3c.dom.Element)childNodes.item(i);
                Element child = this.buildXmlSchemaElementFromElement(xchildElement, element, parentFile);
                children.add(child);
            }
        }
            
        
        return element;
    }

    
    /**
     * Builds a XmlSchema element given an xml attribute.
     * @param xmlAttribute The attribute used to create the XmlSchema element.
     * @param parent The parent XmlSchema element. This is null if there is no parent.
     * @param parentFile The file that contains the atribute.
     * @return The created XmlSchema element.
     * @throws OdbgenException 
     */
    private Element buildXmlSchemaElementFromAttribute(
            Attr xmlAttribute, 
            Element parent,
            File parentFile
            ) throws OdbgenException {
        String name = xmlAttribute.getNodeName();
        String content = xmlAttribute.getTextContent();
        Element element = new Element(
                name,
                parent, 
                new ArrayList<Element>(),
                content
                );
        element.setFilePath(parentFile.getAbsolutePath());
        
        return element;
    }

}


















